//
// Created by guoshunw on 23-12-14.
//

#include <csignal>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <linux/if.h>
#include <linux/if_tun.h>

#include <iomanip>
#include <memory>
#include <sys/ioctl.h>

// https://blog.haohtml.com/archives/31687
// https://www.cnblogs.com/charlieroro/p/13497340.html

static const char pkt_data[] =
        "\x3c\xfd\xfe\x9e\x7f\x71\xec\xb1\xd7\x98\x3a\xc0\x08\x00\x45\x00"
        "\x00\x2e\x00\x00\x00\x00\x40\x11\x88\x97\x05\x08\x07\x08\xc8\x14"
        "\x1e\x04\x10\x92\x10\x92\x00\x1a\x6d\xa3\x34\x33\x1f\x69\x40\x6b"
        "\x54\x59\xb6\x14\x2d\x11\x44\xbf\xaf\xd9\xbe\xaa";

enum class MODE {
    SERVER,
    CLIENT,
};

int tun_alloc(char *dev, short flags = 0) {
#define TUN_DEV_PATH "/dev/net/tun"
    auto fd = open(TUN_DEV_PATH, O_RDWR);
    if (fd < 0) {
        perror("Opening " TUN_DEV_PATH);
        return fd;
    }
    ifreq ifr{};
    ifr.ifr_flags = flags;

    if (dev) {
        strncpy(ifr.ifr_name, dev, IFNAMSIZ);
    }

    auto err = ioctl(fd, TUNSETIFF, static_cast<void *>(&ifr));
    if (err < 0) {
        perror("ioctl TUNSETIFF");
        close(fd);
        return err;
    }

    strcpy(dev, ifr.ifr_name);

    return fd;
}

int tun_test(int argc, const char *argv[]) {

    if (argc < 2) {
        std::cout << "Usage: " << argv[0] << " sender/receiver" << std::endl;
        return 1;
    }

    auto is_sender = "sender" == std::string(argv[1]);

    char tun_name[IFNAMSIZ] = "tun0";
    uint8_t buf[4096];
    auto flags = static_cast<short>(IFF_TUN | IFF_NO_PI);
    if (flags & IFF_TAP) {
        strncpy(tun_name, "tap0", IFNAMSIZ);
    }

    auto dev_fd = tun_alloc(tun_name, flags);
    if (dev_fd < 0) {
        std::cout << "Allocate device " << tun_name << " failed" << std::endl;
        return dev_fd;
    }

    //    auto dev_fd1 = tun_alloc(tun_name, flags);
    //    if (dev_fd1 < 0) {
    //        std::cout << "Allocate device " << tun_name << " failed" << std::endl;
    //        return dev_fd;
    //    }
    //    std::cout << "Open device " << tun_name << " success, fd: " << dev_fd1 << std::endl;


    std::shared_ptr<int> dev_fd_guard(&dev_fd, [](const int *fd) {
        if (nullptr == fd || *fd < 0) return;
        std::cout << "Closing device fd " << *fd << std::endl;
        if (close(*fd) < 0) {
            perror("close device fd error");
        }
    });


    std::cout << "Allocated device " << tun_name << "(fd: " << dev_fd << ") for " << (is_sender ? "sender" : "receiver") << std::endl;

    if (is_sender) {
        while (true) {
            auto n_write = write(dev_fd, pkt_data, sizeof(pkt_data));
            if (n_write < 0) {
                //                printf("error: (%d)%s\n", errno, strerror(errno));
                perror("writing data error");
                break;
            }

            std::cout << "Write " << n_write << " bytes" << std::endl;
            sleep(1);
        }
    } else {
        // sudo ip link set dev tun0 up && sudo ip addr add 192.168.2.114/24 dev tun0

        while (true) {
            auto n_read = read(dev_fd, buf, sizeof(buf));
            if (n_read < 0) {
                perror("reading data error");
                break;
            }

            std::cout << "Read " << n_read << " bytes from device " << tun_name << std::endl;
            std::cout << to_hex(buf, n_read) << std::endl;
        }
    }
    return 0;
}
